#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <ctype.h>
#include <fcntl.h>

#include <openssl/md5.h>
#include <openssl/sha.h>

#define BUF_SIZE 1024
#define DIGEST_LENGTH 1024


typedef int (*file_get_digest)(const char* file_path, char* digest_buf, size_t buf_size);

/*
 * file_get_sha256, get sha256sum for file with file_path
 * return 0 if succeed, or -1 if not
 */
static int file_get_sha256(const char* file_path, char* digest_buf, size_t buf_size)
{
	unsigned char md[SHA256_DIGEST_LENGTH];
	char digest_str[2 * SHA256_DIGEST_LENGTH];
	unsigned char buf[BUF_SIZE];
	int i;
	int fd;
	int ret = -1;
	SHA256_CTX ctx;
	int bytes;

	if(!digest_buf)
		return -1;

	if(buf_size < 2 * SHA256_DIGEST_LENGTH)
		return -1;

	fd = open(file_path, O_RDONLY);
	if(fd < 0)
		return -1;

	memset(buf, 0, BUF_SIZE);

	SHA256_Init(&ctx);
	while((bytes = read(fd, buf, BUF_SIZE)) > 0)
		SHA256_Update(&ctx, buf, bytes);
	SHA256_Final(md, &ctx);

	if(bytes < 0)
		goto file_get_sha256_out;

	for(i = 0; i < SHA256_DIGEST_LENGTH; i++)
		sprintf(&digest_str[2 * i], "%02x", md[i] & 0xff);

	for(i = 0; i < 2 * SHA256_DIGEST_LENGTH; i++)
	{
		if(isupper(digest_str[i]))
			digest_str[i] = tolower(digest_str[i]);
	}

	memcpy(digest_buf, digest_str, 2 * SHA256_DIGEST_LENGTH);

	ret = 0;

file_get_sha256_out:

	close(fd);
	return ret;
}

/*
 * file_get_sha1, get sha1sum for file with file_path
 * return 0 if succeed, or -1 if not
 */
static int file_get_sha1(const char* file_path, char* digest_buf, size_t buf_size)
{
	unsigned char md[SHA_DIGEST_LENGTH];
	char digest_str[2 * SHA_DIGEST_LENGTH];
	unsigned char buf[BUF_SIZE];
	int i;
	int fd;
	int ret = -1;
	SHA_CTX ctx;
	int bytes;

	if(!digest_buf)
		return -1;

	if(buf_size < 2 * SHA_DIGEST_LENGTH)
		return -1;

	fd = open(file_path, O_RDONLY);
	if(fd < 0)
		return -1;

	memset(buf, 0, BUF_SIZE);

	SHA1_Init(&ctx);
	while((bytes = read(fd, buf, BUF_SIZE)) > 0)
		SHA1_Update(&ctx, buf, bytes);
	SHA1_Final(md, &ctx);

	if(bytes < 0)
		goto file_get_sha1_out;

	for(i = 0; i < SHA_DIGEST_LENGTH; i++)
		sprintf(&digest_str[2 * i], "%02x", md[i] & 0xff);

	for(i = 0; i < 2 * SHA_DIGEST_LENGTH; i++)
	{
		if(isupper(digest_str[i]))
			digest_str[i] = tolower(digest_str[i]);
	}

	memcpy(digest_buf, digest_str, 2 * SHA_DIGEST_LENGTH);

	ret = 0;

file_get_sha1_out:

	close(fd);
	return ret;
}

/*
 * file_get_md5, get md5sum for file with file_path
 * return 0 if succeed, or -1 if not
 */
static int file_get_md5(const char* file_path, char* digest_buf, size_t buf_size)
{
	unsigned char md[MD5_DIGEST_LENGTH];
	char digest_str[2 * MD5_DIGEST_LENGTH];
	unsigned char buf[BUF_SIZE];
	int i;
	int fd;
	int ret = -1;
	MD5_CTX ctx;
	int bytes;

	if(!digest_buf)
		return -1;

	if(buf_size < 2 * MD5_DIGEST_LENGTH)
		return -1;

	fd = open(file_path, O_RDONLY);
	if(fd < 0)
		return -1;

	memset(buf, 0, BUF_SIZE);

	MD5_Init(&ctx);
	while((bytes = read(fd, buf, BUF_SIZE)) > 0)
		MD5_Update(&ctx, buf, bytes);
	MD5_Final(md, &ctx);

	if(bytes < 0)
		goto file_get_md5_out;

	for(i = 0; i < MD5_DIGEST_LENGTH; i++)
		sprintf(&digest_str[2 * i], "%02x", md[i] & 0xff);

	for(i = 0; i < 2 * MD5_DIGEST_LENGTH; i++)
	{
		if(isupper(digest_str[i]))
			digest_str[i] = tolower(digest_str[i]);
	}

	memcpy(digest_buf, digest_str, 2 * MD5_DIGEST_LENGTH);

	ret = 0;

file_get_md5_out:

	close(fd);
	return ret;
}

/*
 * _file_check_digest, check file digest with _file_get_digest function
 * return 1 if check ok, or 0 if not
 */
static int _file_check_digest(const char* file_path, const char* digest_string, file_get_digest _file_get_digest, size_t digest_length)
{
	int ret = 0;
	char digest_str[DIGEST_LENGTH];

	if(NULL == _file_get_digest)
		return 0;

	if(!digest_string)
		return 0;

	memset(digest_str, 0, sizeof(digest_str));

	ret = _file_get_digest(file_path, digest_str, sizeof(digest_str));
	if(-1 == ret)
		return 0;
	else if(0 == ret)
		return (0 == strncmp(digest_string, digest_str, 2 * digest_length));

	return 0;
}

/*
 * file_check_sha256, check file digest with sha256
 * return 1 if check ok, or 0 if not
 */
int file_check_sha256(const char* file_path, const char* digest_string)
{
	return _file_check_digest(file_path, digest_string, file_get_sha256, SHA256_DIGEST_LENGTH);
}

/*
 * file_check_sha1, check file digest with sha1
 * return 1 if check ok, or 0 if not
 */
int file_check_sha1(const char* file_path, const char* digest_string)
{
	return _file_check_digest(file_path, digest_string, file_get_sha1, SHA_DIGEST_LENGTH);
}

/*
 * file_check_md5, check file digest with md5
 * return 1 if check ok, or 0 if not
 */
int file_check_md5(const char* file_path, const char* digest_string)
{
	return _file_check_digest(file_path, digest_string, file_get_md5, MD5_DIGEST_LENGTH);
}

